package czy.demo.config;

import czy.demo.service.JdbcUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

import javax.sql.DataSource;
/**
 * 使用数据库jdbc进行用户认证的配置
 * @EnableWebSecurity：启用spring web 安全,这个注解上使用了@EnableGlobalAuthentication
 * @EnableGlobalMethodSecurity(prePostEnabled = true)：启用方法级别的安全，并且启用Pre、Post注解
 */
@Order(1)
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication
@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true,securedEnabled = true)
public class JdbcAuthConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JdbcUserDetailsService jdbcUserDetailsService;

    /**
     * 认证管理构建器，用于配置用户信息，如：内存、数据库等
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {

        /**
         * 使用数据库管理用户、权限、组、组权限，此时需要配置DataSource
         */
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                //数据库中存储的角色为不带前缀的，而需要的角色为带有ROLE_前缀的，所以这里需要设置一个统一前缀
                .rolePrefix("ROLE_")
                //密码解码器，这里的解码器不做任何编码，只是使用字符串相等匹配
                .passwordEncoder(NoOpPasswordEncoder.getInstance())
                .and()
                .userDetailsService(jdbcUserDetailsService);
        /* 使用指定的用户详情服务 */

    }


    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return jdbcUserDetailsService;
    }

    @Override
    protected UserDetailsService userDetailsService() {
        return jdbcUserDetailsService;
    }

    /**
     * 这个我也不知道干嘛的
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    /**
     * 配置不同http请求的安全策略
     * 注意这里的权限配置从上到下是递进的
     * 即需要的权限越来越高
     * 1：对于同一个路由，会首先匹配比较靠上的权限规则，如果通过，下面的权限规则不会执行
     * 2：下面的权限规则会覆盖上面的
     * 到底是哪种，还没测出来
     *
     * 关于角色：
     * 由于一个用户可以拥有多个角色，所以每个角色可以执行的接口权限不应重复，最好明确分界
     * 如：常识中ADMIN管理员角色应该是大于USER角色的，但是他们所能访问的接口权限是明确分开的
     * 并不是拥有ADMIN角色，就能访问需要USER角色的接口
     * 如果要实现一个用户既可以访问ADMIN角色的接口，也可以访问USER角色的接口，可以让它拥有多个角色即可
     * 角色之间最好不要有权限交叉
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* 获取ExpressionInterceptUrlRegistry，获取表达式url拦截注册器 */
        http.authorizeRequests()
                /* 所有用户都可以访问，无论匿名与否 */
                .antMatchers("/permit/all/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/principal/**").permitAll()
                /* 所有用户都无法访问 */
                .antMatchers("/deny/all").denyAll()
                /* 只有匿名用户可以访问，认证后无法访问 */
                .antMatchers("/anonymous/**").anonymous()
                /* 必须认证之后才能访问 */
                .antMatchers("/authenticated/**").authenticated()
                /* 具有指定角色之一可以访问 */
                .antMatchers("/any/**").hasAnyRole("USER","ADMIN")
                /* 必须具有指定权限，才能访问 */
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                /* 具有指定ip才能访问*/
                .antMatchers("/ip/**").hasIpAddress("192.168.1.174")
                /* 首先匹配指定AuthorizedUrl，然后设置其认证、授权级别，返回ExpressionInterceptUrlRegistry */
                .anyRequest().authenticated()
                /* 获取HttpSecurity本身 */
                .and()
                /* 应用表单登录配置，返回FormLoginConfigurer，这个配置会启用一个简单的登录表单 */
                .formLogin()
                /* 获取HttpSecurity本身 */
                .and()
                /* 应用HttpBasicConfigurer，当表单认证与Basic认证同时启用时，前者覆盖后者 */
                .httpBasic();
    }
}
